Add persistent session support#28
Merged
Merged
Conversation
Lets a client run N sequential invocation/command_execution requests over a single WebSocket. Adds end_session_message (client -> server) and reworks the Node session around per-request AbortController/ArtifactManager so cancel aborts only the in-flight request. Python: new RuntimeUseSession + RuntimeUseClient.session() context manager; one-shot query()/execute_commands() preserved as convenience wrappers. Bumps runtimeuse and runtimeuse-client to 0.11.0.
Post-agent command failures previously threw out of run(), causing the session catch block to replace the successful agent result with an error_message — the client would never see the actual result. Now post-agent failures emit an error_message (so the client is notified) but the agent's result_message is still returned as the terminal.
runCommands emitted error_message on non-zero exit and also threw, so pre-agent failures produced one frame and post-agent failures produced two (both from runCommands and from the subsequent catch block I added). Either way, the error_message is terminal on the wire — the Python client raises on it and never consumes result_message. Now runCommands only throws; the session's outer catch emits a single error_message for pre-agent failures, and post-agent failures are logged server-side so result_message remains the sole terminal.
Wrap handleRequest's body in try/finally so requestInFlight, the abort controller, and the artifact manager are always cleared. Previously, if stopWatching / waitForPendingRequests / waitForAll threw (or were cancelled), requestInFlight stayed true and every subsequent request was rejected with "another is in flight", defeating the whole point of a persistent session. Also swallow drain errors (log only) so a watcher failure can't abort the terminal send.
Preserve the pre-refactor behavior: a failed post-agent command terminates the request with error_message, so the Python client raises AgentRuntimeError. runCommands still doesn't emit error_message itself — the session's outer catch is the single wire emit point, so there's no double-emit.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 55fd083. Configure here.
Previously the watcher was torn down and recreated per request, which forced each request to block on a 3s chokidar-drain window before the terminal could be sent — so sequential calls on a persistent session were spaced by the delay. Now ArtifactManager lives for the whole session and exposes addDirectory() so each request just registers its artifacts_dir with the existing watcher. The drain (3s delay + stopWatching + waitForPendingRequests + waitForAll) happens once, on session close, catching files written right before the ws closed. Requests return as soon as the agent (and post-agent commands) finish. Also tightens mocks in session.test.ts (mockReset in beforeEach) and covers the new behavior with "stops watcher on close, not per request" and "registers each request's artifacts_dir on the shared watcher". Additionally pulls in prior fixes in this branch: - _run_request_loop drains the server's terminal before raising CancelledException, preventing stale error_messages from leaking into the next request on a persistent session. - command-handler coerces non-numeric error.code (e.g. ABORT_ERR) to -1. - session overrides terminal with "Request cancelled" when the runner returned a partial result after abort.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
end_session_messagein the wire protocol; Node session now runs N sequentialinvocation_message/command_execution_messageexchanges over one WebSocket, with per-requestAbortController/ArtifactManagersocancel_messageaborts only the in-flight request.RuntimeUseSessionandRuntimeUseClient.session()async-context-manager for persistent sessions; existing one-shotquery()/execute_commands()kept as convenience wrappers over the same transport.runtimeuseandruntimeuse-clientto 0.11.0.Test plan
npm testinpackages/runtimeuse(136 passing, incl. new "two sequential command_execution_messages on one socket" case)pytestinpackages/runtimeuse-client-python(43 passing, incl. newTestPersistentSessioncases: two sequential calls, mixed query+commands, cancel mid-session + follow-up, per-call abort, close-on-exit)Note
Medium Risk
Introduces a new persistent request/response mode over a single WebSocket and updates cancellation/terminal-message behavior, which could affect protocol compatibility and edge cases around abort/cleanup.
Overview
Adds persistent session support across the runtime and Python client: a new
end_session_messageallows multiple sequentialinvocation_message/command_execution_messageexchanges over one WebSocket without reconnecting.On the Node runtime,
WebSocketSessionnow treats each invocation/command as an isolated request with its ownAbortController, supportscancel_messageto abort only the in-flight request (without closing the socket), and keeps a session-scopedArtifactManagerthat can watch multipleartifacts_dirvalues and defers watcher draining/finalization to session close.On the Python client, introduces
RuntimeUseClient.session()/RuntimeUseSessionbuilt on new persistent transport interfaces (PersistentTransport,ConnectedTransport) and refactors message handling into a shared request loop that drains server terminals after abort to prevent stale errors leaking into subsequent calls. Versions are bumped to0.11.0and tests/examples are added/updated accordingly.Reviewed by Cursor Bugbot for commit d49018f. Bugbot is set up for automated code reviews on this repo. Configure here.